package com.ian.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter;
import org.springframework.security.web.header.writers.DelegatingRequestMatcherHeaderWriter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy.*;
import static org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter.Directive.*;

/**
 * Security Config
 * @author Witt
 * @version 1.0.0
 * @date 2022/5/7
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(requests -> requests
                        .antMatchers(
//                                "/",
//                                "/*.html",
//                                "/**/*.html",
                                "/**/*.css",
                                "/**/*.js",
                                "/js/**",
                                "/css/**",
                                "/img/**",
                                "/test",
                                "/csp-report-endpoint/").permitAll()
                        .anyRequest().authenticated())
                // formLogin()配置是必须的，哪怕是使用默认配置。否则，当访问 /login 时，系统报404错误，说明系统不会自动渲染或绑定/login
                .formLogin(formLogin -> formLogin.permitAll())
                /*
                 By default, Spring Security disables rendering within an iframe using X-Frame-Options with the following config in the browser:
                     X-Frame-Options: DENY
                 You can customize frame options to use the same origin within a Configuration using the following config statement.
                 and the config in the browser becomes:（方法1）
                    X-Frame-Options: SAMEORIGIN
                 */
//                 .headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin()))
                /*
                cacheControl() 和 cacheControl(withDefaults()) 的区别：
                    两者实现的功能都一样，主要是返回值不同，cacheControl() 返回 CacheControlConfig 对象，
                    而 cacheControl(Customizer.withDefaults()) 返回 HeadersConfigurer对象，
                    而 cacheControl()方法后，跟上and()方法，就能返回 HeadersConfigurer对象。
                    其他地方也是类似的编程风格。
                 */
                //  disable the defaults, only add Cache Control.
//                .headers(headers -> headers.defaultsDisabled().cacheControl(withDefaults()))
                // disable all of the HTTP Security response headers
//                .headers(headers -> headers.disable())
                // disable Spring Security’s cache control HTTP response headers
//                .headers(headers -> headers.cacheControl(cache -> cache.disable()))
                /*
                 Spring Security includes Content-Type-Options headers by default. However, you can disable it with the following config
                 Spring Security disables content sniffing by default by adding the following header to HTTP responses:
                    X-Content-Type-Options: nosniff
                 */
//                .headers(headers -> headers.contentTypeOptions(contentTypeOptions -> contentTypeOptions.disable()))
                // Spring Security provides the Strict Transport Security header by default. However, you can customize the results explicitly
//                .headers(headers -> headers
//                    .httpStrictTransportSecurity(hsts -> hsts
//                        .includeSubDomains(true)
//                        .preload(true)
//                        .maxAgeInSeconds(31536000)))
                // For passivity reasons, Spring Security provides servlet support for HTTP Public Key Pinning but it is no longer recommended.
//                .headers(headers -> headers
//                    .httpPublicKeyPinning(hpkp -> hpkp
//                        .includeSubDomains(true)
//                        .reportUri("https://example.net/pkp-report")
//                        .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=")))
                /*
                 By default, Spring Security instructs browsers to block reflected XSS attacks using the <<headers-xss-protection,X-XSS-Protection header>.
                 浏览器的响应标头为：X-XSS-Protection: 1; mode=block
                 However, you can change this default. 禁用该保护后，浏览器的响应标头变为：X-XSS-Protection: 1
                 */
//                .headers(headers -> headers.xssProtection(xss -> xss.block(false)))
                // Spring Security does not add Content Security Policy by default, because a reasonable default is impossible to know without context of the application
//                .headers(headers -> headers
//                        .contentSecurityPolicy(csp -> csp.
//                            policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
//                            .reportOnly()))
                // Spring Security does not add Referrer Policy headers by default
//                .headers(headers -> headers.referrerPolicy(referrer -> referrer.policy(SAME_ORIGIN)))
                // Spring Security does not add Feature Policy headers by default
//                .headers(headers -> headers.featurePolicy("geolocation 'self'"))
                // Spring Security does not add Permissions Policy headers by default
//                .headers(headers -> headers.permissionsPolicy(permissionsPolicy -> permissionsPolicy.policy("geolocation=(self)")))
                // Spring Security does not add Clear-Site-Data headers by default
                //   the configured items can be sent on log out with the following configuration
//                .logout(logout -> logout.addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(CACHE, COOKIES))))
                // Spring Security provides hooks to enable adding custom headers
//                .headers(headers -> headers.addHeaderWriter(new StaticHeadersWriter("X-Custom-Security-Header", "header-value1", "header-value2")))
                // 方法2：to configure this "X-Frame-Options: SAMEORIGIN"，上面有方法1
//                .headers(headers -> headers.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
        ;
        // At times you may want to only write a header for certain requests. For example, perhaps you want to only protect your log in page from being framed
        RequestMatcher requestMatcher = new AntPathRequestMatcher("/login");
        DelegatingRequestMatcherHeaderWriter headerWriter = new DelegatingRequestMatcherHeaderWriter(requestMatcher, new XFrameOptionsHeaderWriter());
        http.
                headers(headers -> headers
                        .frameOptions().disable()
                        .addHeaderWriter(headerWriter));
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("user").password("123").roles("user").passwordEncoder(pw -> "{bcrypt}" + new BCryptPasswordEncoder().encode(pw)).build();
        // UserDetails user = User.withUsername("user").password("{bcrypt}$2a$10$lmURdwCsjA9jFc5NgzX1Keq5O91WEuOOgEtMQ246X.G/cu.HSqMTG").roles("user").build();
        UserDetails admin = User.withUsername("admin").password("123").roles("admin").passwordEncoder(pw -> "{bcrypt}" + new BCryptPasswordEncoder().encode(pw)).build();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(user);
        manager.createUser(admin);
        return manager;
    }
}
